package cn.xinfei.xdecision.engine.runner.executor;

import cn.xinfei.xdecision.common.model.enginex.po.EngineNode;
import cn.xinfei.xdecision.common.utils.enums.enginex.NodeTypeEnum;
import cn.xinfei.xdecision.common.utils.util.JsonUtil;
import cn.xinfei.xdecision.common.utils.util.JsonUtils;
import cn.xinfei.xdecision.constant.EngineResultEnum;
import cn.xinfei.xdecision.engine.runner.context.PipelineContext;
import cn.xinfei.xdecision.engine.runner.context.PipelineContextHolder;
import cn.xinfei.xdecision.engine.runner.context.PipelineMetaInfoContext;
import cn.xinfei.xdecision.engine.runner.context.XDecisionContext;
import cn.xinfei.xdecision.engine.runner.context.model.TrackModel;
import cn.xinfei.xdecision.engine.runner.executor.model.StateTransmitModel;
import cn.xinfei.xdecision.engine.runner.executor.strategy.EngineNodeRunner;
import com.dianping.cat.Cat;
import feign.RetryableException;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tags;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeoutException;

import static cn.xinfei.xdecision.constant.EngineResultEnum.*;

@Slf4j
public class NodeHandler extends AbsNodeHandler<NodeHandler> {

    private String engineCode;
    private EngineNode engineNode;

    public NodeHandler(EngineNode engineNode, String nodeType) {
        super(nodeType, EngineNodeRunner.PRE);
        this.engineNode = engineNode;
        this.engineCode = engineNode.getEngineCode();
    }

    public void initContext() {
        XDecisionContext context = XDecisionContext.getContext();
        PipelineContext pipelineContext = context.getPipelineContext();
        ConcurrentHashMap<String, List<TrackModel>> track = pipelineContext.getTrack();
        if (!track.containsKey(engineCode)) {
            track.put(engineCode, new ArrayList<>());
        }
    }


    public StateTransmitModel handler() {
        long start = System.currentTimeMillis();
        StateTransmitModel stateTransmitModel = XDecisionContext.getContext().getStateContext();
        Map<String, Object> nodeInput = new HashMap<>();
        String nodeCode = engineNode.getNodeCode();
        String nodeType = engineNode.getNodeType();
        NodeDurationModel durationModel = new NodeDurationModel();
        try {
            //0.清理掉可能存在的默认值变量
            engineNodeRunner.clear();
            durationModel.setClearDuration();

            //1.加载变量
            engineNodeRunner.getNodeFieldValue(this);
            durationModel.setGetValueDuration();

            //3.记录节点执行轨迹
            this.tracer();
            durationModel.setTrackDuration();

            //4.执行节点逻辑
            stateTransmitModel = engineNodeRunner.runNode(this);
            durationModel.setRunNodeDuration();

            //4.设置组件输出
            engineNodeRunner.setNodeOutput(this);
            durationModel.setSetNodeOutputDuration();

            //6.判断节点是否终止(终止输出)
            boolean terminalCondition = engineNodeRunner.terminalCondition(this, new HashMap<>(), nodeInput);
            durationModel.setTerminalDuration();

            //节点输出，要包含全部的输入输出和过程，凡是作为结果配置的，都进行输出
            //7.获取节点输出
            Map<String, Object> nodeOutput = engineNodeRunner.getNodeOutput(this);
            durationModel.setGetNodeOutputDuration();

            //2.获取节点输入（输出节点放在最后补齐）
            nodeInput.putAll(engineNodeRunner.getNodeInput(this));
            durationModel.setGetNodeInputDuration();

            //8.打印节点日志
            //技术辅助字段，记录节点命中情况
            String terminal = nodeCode + "_terminal";
            long end = System.currentTimeMillis();
            long duration = end - start;
            String nodeDuration = nodeCode + "_duration";
            nodeOutput.put(nodeDuration, duration);
            nodeOutput.put(terminal, terminalCondition);
            engineNodeRunner.after(this, stateTransmitModel, nodeOutput, nodeInput);
            durationModel.setAfterDuration();
            durationModel.setNodeDuration();
            log.info("nodeTerminal:" + terminalCondition + ",nodeType:" + nodeType + "," + JsonUtils.toJsonString(durationModel));

            if (terminalCondition) {
                //命中终止
                log.info("命中终止条件");
                return stateTransmitModel.setEngineResultEnum(SUCCESS_HIT_END);
            }
        } catch (TimeoutException | SocketTimeoutException | RetryableException | com.netflix.client.ClientException e) {
            //7.一个节点执行异常，是否继续走后面的节点???
            log.error("节点执行超时异常,决策流终止，error={}", e.getMessage(), e);
            Cat.logError(e);
            Metrics.counter("xdecision_error_total", Tags.of("node", this.nodeType)).increment();
            stateTransmitModel.setEngineResultEnum(DATASOURCE_TIMEOUT);
        } catch (Exception e) {
            //7.一个节点执行异常，是否继续走后面的节点???
            log.error("节点执行异常,决策流终止，error={}", e.getMessage(), e);
            Cat.logError(e);
            Metrics.counter("xdecision_error_total", Tags.of("node", this.nodeType)).increment();
            stateTransmitModel.setEngineResultEnum(EXCEPTION_END);
        }

        //返回下一个节点
        return stateTransmitModel;
    }

    public void push() {
        EngineNode engineNode = this.getEngineNode();
        String engineCode = engineNode.getEngineCode();
        String nodeCode = engineNode.getNodeCode();
        String nodeType = this.getNodeType();
        if (nodeType.equals(NodeTypeEnum.CHILD_ENGINE.getType())) {
            //子引擎的stack放的是下一个节点
            nodeCode = engineNode.getNextNodeCodes();
        }
        PipelineContextHolder.pushNodeCode(engineCode, nodeCode, PipelineContextHolder.getDecisionId());
    }

    public void pop(StateTransmitModel transmitModel) {
        EngineResultEnum engineResultEnum = transmitModel.getEngineResultEnum();
        String result = engineResultEnum.getResult();
        //只要不是pending，说明执行过去了，就要pop
        if (!result.equals(EngineResultEnum.PENDING.getResult())) {
            String nodeCode = engineNode.getNodeCode();
            PipelineContextHolder.popNodeContextModel(nodeCode);
        } else if (result.equals(EngineResultEnum.EXCEPTION.getResult())) {
            //todo shiyusen
            log.error("出现了exception，需要考虑边界问题");
        } else {
        }
//        String nodeCode = engineNode.getNodeCode();
//        PipelineContextHolder.popNodeContextModel(nodeCode);
    }

    /**
     * 记录执行过的节点code
     *
     * @param
     */
    public void tracer() {
        try {
            PipelineContextHolder.putInTrace(this);
        } catch (Exception e) {
            log.error("tracer fail.error={},nodeHandler={}", e.getMessage(),
                    JsonUtil.toJson(this), e);
        }
    }

    @Override
    public NodeHandler next(StateTransmitModel stateTransmitModel) {
        XDecisionContext context = XDecisionContext.getContext();
        PipelineMetaInfoContext pipelineMetaInfoContext = context.getPipelineMetaInfoContext();
        String nextNodes = stateTransmitModel.getNextNodeCode();
        if (StringUtils.isNotEmpty(nextNodes)) {
            NodeHandler nextNodeHandler = pipelineMetaInfoContext.get(nextNodes);
            if (this.getNodeType().equals(NodeTypeEnum.PARALLEL.getType())) {
                //如果当前节点是并行节点，此处返回的nextNodes是聚合节点
                return nextNodeHandler;
            }
            if (this.getNodeType().equals(NodeTypeEnum.CHAMPION_CHALLENGE.getType())) {
                return nextNodeHandler;
            }
            if (this.getNodeType().equals(NodeTypeEnum.CLASSIFY.getType())) {
                //执行分组节点逻辑时会输出 nextNode节点的信息，并存储在了interim对象里，所以，如果输出的interim里面有nextNode，则说明有分组，需要走分组下面的节点
                return nextNodeHandler;
            }
            if (this.getNodeType().equals(NodeTypeEnum.AGGREGATION.getType())) {
                //如果当前节点是个聚合节点，则返回下一个节点继续执行
                return nextNodeHandler;
//                } else if (curr.getNodeType().equals(NodeTypeEnum.CREDIT_REPORT.getType())) {
                // 此处是人行节点，重置返回节点，改为返回了pending状态 ；人行节点执行完毕后，线程要pending，所以此处跳出，结束
//                    log.warn("credit node break,return pending");
//                    break;

//                } else if (nextNodeHandler != null && nextNodeHandler.getNodeType().equals(NodeTypeEnum.END.getType())) {
                //如果下一个节点是end节点，则直接结束
//                    log.warn("next node is end,return success");
//                    transmitModel = stateTransmitModel.setEngineResultEnum(EngineResultEnum.SUCCESS_END);
//                    break;
            }
            return nextNodeHandler;
        }
        return null;
    }

    @Override
    public boolean end(StateTransmitModel stateTransmitModel) {
        XDecisionContext context = XDecisionContext.getContext();
        PipelineMetaInfoContext pipelineMetaInfoContext = context.getPipelineMetaInfoContext();
        String nextNodes = stateTransmitModel.getNextNodeCode();

        EngineResultEnum engineResultEnum = stateTransmitModel.getEngineResultEnum();
        if (engineResultEnum != null && engineResultEnum != EngineResultEnum.PROCESSING) {
            log.warn("进入[{}]终止状态", engineResultEnum);
            return true;
        }

        if (StringUtils.isNotEmpty(nextNodes)) {
            NodeHandler nextNodeHandler = pipelineMetaInfoContext.get(nextNodes);
            if (null == nextNodeHandler) {
                log.warn("next node is null,break.nextNodes={}", nextNodes);
                stateTransmitModel.setEngineResultEnum(EngineResultEnum.EXCEPTION_NO_NEXT_END);
                return true;
            }
            if (nextNodeHandler.getNodeType().equals(NodeTypeEnum.AGGREGATION.getType())) {
                //并行节点后面的分支为多线程执行，执行到聚合节点则结束
                //如果下一个节点是聚合节点，则直接结束，返回给主线程
                //如果不是并行节点后的聚合节点，是不会执行聚合节点的
                log.warn("next node is aggregation,return aggregation node");
                return true;
            }
            //既没有终止态，而且还有nextCode。为什么要有两个判断条件，不进行统一？
            log.info("goon，currentCode={},nextNodeCode={}", engineResultEnum.getCode(), nextNodes);
            return false;
        }

        log.warn("next node is null,break");
        stateTransmitModel.setEngineResultEnum(EngineResultEnum.EXCEPTION_NO_NEXT_END);
        return true;
    }

    @Override
    public String getCode() {
        return engineCode;
    }

    @Override
    public AbsNodeHandler getHandler() {
        return this;
    }


    public String getNextNodes() {
        return engineNode.getNextNodeCodes();
    }

    public String getEngineCode() {
        return engineCode;
    }

    public void setEngineCode(String engineCode) {
        this.engineCode = engineCode;
    }

    public EngineNode getEngineNode() {
        return engineNode;
    }

    public void setEngineNode(EngineNode engineNode) {
        this.engineNode = engineNode;
    }
}
